// DBInterfaceMySQL.cpp
// DBMySQL
// MySQL Database driver for the Protea Project / Provides interface to MySQL
// Copyright (C) 2003, 2004, 2005 eXegeSys, Inc.
// Copyright (C) 2008 Bruce A. James
//
// This program is part of the Protea Project.
//
// The Protea Project is free software; you can redistribute it and/or modify it 
// under the terms of the GNU General Public License as published by the 
// Free Software Foundation, either version 3 of the License, or (at your 
// option) any later version.
//
// The Protea Project is distributed in the hope that it will be useful, but 
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 
// for more details.
// 
// You should have received a copy of the GNU General Public License along 
// with this program.  If not, see <http://www.gnu.org/licenses/>.
//
// Please send correspondence to:
//
// theproteaproject@gmail.com
//
// Include basic Protea functionality
#include "../../defines.h"

// MySQL definitions
#include "mysql.h"

#include "DBInterfaceMySQL.h"
#include "../../defns.h"
using namespace ProteaApp1;

// Global Functions
////////////////////

db_type XCRegisteredDBI()
{
	return db_mysql;
};

// new XCDBInterfaceMySQL object
XCDBInterface* MakeInterface( XCDBHandlerConnection* p, XCAppInterface* app ) {
	return new XCDBInterfaceMySQL( p, app );
};



void HANDLE_MYSQL_ERROR( MYSQL* env ) {
	INFUNC( HandleDBError, NULL );

	int sql_error = mysql_errno( env );

	const long ERROR_SIZE = 1024;
	char temp[ ERROR_SIZE ];

	snprintf( temp, ERROR_SIZE, "%d: %s\n", sql_error, mysql_error( env ) );

	RAISETOOLSERROR_1PARAM( ERR_DATABASE, temp );

	OUTFUNC();
};



// Interface Functions
// Supported by all drivers
/////////////////////////////////////////////////////////
XCDBInterfaceMySQL::XCDBInterfaceMySQL( XCDBHandlerConnection* p, XCAppInterface* app )
{
	INFUNC( XCDBInterfaceMySQL::XCDBInterfaceMySQL, app )

	myapp = app;
	parent = p;

	// Initialize the descriptor
	descriptor = new XCDBDriverDescr();
	memset( descriptor, 0, sizeof( XCDBDriverDescr ) );
	descriptor->type = db_mysql;
	descriptor->version = 1;
    descriptor->can_two_phase_commit = false;
	descriptor->can_update_defaults = true;
	descriptor->can_exclusive_access = true;	// *** investigate, we just heard this from Travis *** //
	descriptor->can_drop_fields = true;			// Able to drop fields w/o rebuild
	descriptor->datetimeonly = false;
	descriptor->varchar_max_size = 65535;
	descriptor->blob_max_size = 65535;
	descriptor->uses_default_constraint = false;
	descriptor->supports_table_rename = true;
	descriptor->requires_manual_table_drop = true;

	// descriptor->AddError( xcerr, dberr );
	descriptor->size = sizeof( XCDBDriverDescr ) + ( 0 * sizeof( XCDBErrorMap ) );

	// Allocate environment handle
	if (!mysql_init( &environment )) {
		// Unable to initialize the driver
		throw;
	};

	OUTFUNC()
};


XCDBInterfaceMySQL::~XCDBInterfaceMySQL()
{
	INFUNC( XCDBInterfaceMySQL::~XCDBInterfaceMySQL, myapp )

	// WHY DO WE GPF HERE?
	// delete descriptor;
	// Exit stuff

	// Free our allocate environment
	mysql_close( &environment );

	OUTFUNC()
};


XCDBConnection* XCDBInterfaceMySQL::Connect( const char* uid, const char* pass )
{
	INFUNC( XCDBInterfaceMySQL::Connect, myapp )

	// Allocate connection handle
	XCDBConnectionMySQL* conn = new XCDBConnectionMySQL( this );
	conn->Connect( uid, pass, parent->defns->name );

	OUTFUNCRET( conn );
};


XCDBConnectionMySQL::XCDBConnectionMySQL( XCDBInterfaceMySQL* dbint ) {
	// Maintain a pointer to the environment
	hDB = dbint;
};



void XCDBConnectionMySQL::Connect( const char* uid, const char* pass, const char* database ) {
	// Connect to database
//	cout << "Connect: name = '" << database << "' uid='" << uid << "' pass='" << pass << "'" << endl;

	// Connect to the MySQL database
	conn = mysql_real_connect( &hDB->environment, NULL, uid, pass, database, 0, NULL, 0 );
	if (!conn)
		HANDLE_MYSQL_ERROR( &hDB->environment );
};



void XCDBConnectionMySQL::Disconnect()
{
	INFUNC( XCDBConnectionMySQL::Disconnect, hDB->myapp )

	// TODO: Figure out how to disconnect

	OUTFUNC()
};

bool XCDBConnectionMySQL::Refresh()
{
	INFUNC( XCDBInterfaceMySQL::Refresh, hDB->myapp )
	OUTFUNCRET( false )
};


XCDBTransaction* XCDBConnectionMySQL::CreateTxn() {
	INFUNC( XCDBInterfaceMySQL::CreateTxn, hDB->myapp )
	XCDBTransactionMySQL* temp = new XCDBTransactionMySQL( this, hDB );
	OUTFUNCRET( temp )
};




void XCDBInterfaceMySQL::CreateDB( char* name, XCDBTransaction* tTxn )
{
	INFUNC( XCDBInterfaceMySQL::CreateDB, myapp );
	;
	OUTFUNC()
};

void XCDBInterfaceMySQL::OpenExclusive()
{
	INFUNC( XCDBInterfaceMySQL::OpenExclusive, myapp )
	;
	OUTFUNC()
};

void XCDBInterfaceMySQL::UserInfo()
{
	INFUNC( XCDBInterfaceMySQL::UserInfo, myapp )
	;
	OUTFUNC()
};



XCDBDriverDescr* XCDBInterfaceMySQL::GetDatabaseInfo() {
	return descriptor;
};





//////////////////////////////////////////////////////
//
// Transaction functions
//
//////////////////////////////////////////////////////
XCDBTransactionMySQL::XCDBTransactionMySQL( XCDBConnectionMySQL* conn, XCDBInterfaceMySQL* dbint )
{
	INFUNC( XCDBTransactionMySQL::XCDBTransactionMySQL, dbint->myapp )
	hConn = conn;
	hDB = dbint;

	dbnumber = hDB->parent->defns->number;
	OUTFUNC()
};


XCDBTransactionMySQL::~XCDBTransactionMySQL()
{
	INFUNC( XCDBTransactionMySQL::~XCDBTransactionMySQL, hDB->myapp )
	;
	OUTFUNC()
};

void XCDBTransactionMySQL::Begin( isolevel il )
{
	INFUNC( XCDBTransactionMySQL::Begin, hDB->myapp )

	// Issue the start command
	if (mysql_query( hConn->conn, "BEGIN WORK" ))
//	if (mysql_query( hConn->conn, "START TRANSACTION" ))
		HANDLE_MYSQL_ERROR( hConn->conn );

	OUTFUNC()
};

void XCDBTransactionMySQL::Commit()
{
	INFUNC( XCDBTransactionMySQL::Commit, hDB->myapp )

	if (mysql_query( hConn->conn, "COMMIT" ))
		HANDLE_MYSQL_ERROR( hConn->conn );

	OUTFUNC()
};

void XCDBTransactionMySQL::Rollback()
{
	INFUNC( XCDBTransactionMySQL::Rollback, hDB->myapp )

	if (mysql_query( hConn->conn, "ROLLBACK" ))
		HANDLE_MYSQL_ERROR( hConn->conn );

	OUTFUNC()
};

void XCDBTransactionMySQL::Prepare()
{
	INFUNC( XCDBTransactionMySQL::Prepare, hDB->myapp )

	// Not supported in this driver
	RAISETOOLSERROR( 1 );

	OUTFUNC()
};

XCDBStatement* XCDBTransactionMySQL::CreateStmt( XCBuffer* buf )
{
	return new XCDBStatementMySQL( hDB, this, buf );
};

XCDBStatement* XCDBTransactionMySQL::CreateStmt2( XCSQLGen* query )
{
	return new XCDBStatementMySQL( hDB, this, query );
};


long XCDBTransactionMySQL::GetDatabaseNumber()
{
	return dbnumber;
};

void XCDBTransactionMySQL::LockTable( XCBuffer* table, XCField* value ) {
	INFUNC( XCDBTransactionMySQL::LockTable, hDB->myapp );
	OUTFUNC();
};





//////////////////////////////////////////////////////
//
// Statement functions
//
//////////////////////////////////////////////////////

XCDBStatementMySQL::XCDBStatementMySQL( XCDBInterfaceMySQL* dbi, XCDBTransactionMySQL* txn, XCBuffer* buf ) : XCDBStatement( dbi->myapp )
{
	INFUNC( XCDBStatementMySQL::XCDBStatementMySQL, dbi->myapp );

	pBuf = buf;

	hTxn = txn;
	hConn = hTxn->hConn;
	hDB = dbi;
	IsEof = false;

	results = NULL;
	fields = NULL;

	stmt = new XCSQLStatementMySQL( hConn );

	OUTFUNC();
};

XCDBStatementMySQL::XCDBStatementMySQL( XCDBInterfaceMySQL* dbi, XCDBTransactionMySQL* txn, XCSQLGen* query ) : XCDBStatement( query )
{
	INFUNC( XCDBStatementMySQL::XCDBStatementMySQL, dbi->myapp );

	pBuf = buffer;
	hTxn = (XCDBTransactionMySQL*)txn;
	hConn = (XCDBConnectionMySQL*)hTxn->hConn;
	hDB = (XCDBInterfaceMySQL*)dbi;
	IsEof = false;

	stmt = new XCSQLStatementMySQL( hConn, query );

	results = NULL;
	fields = NULL;

	OUTFUNC();
};

XCDBStatementMySQL::~XCDBStatementMySQL()
{
	INFUNC( XCDBStatementMySQL::~XCDBStatementMySQL, hDB->myapp )

	if (results)
		mysql_free_result( results );

	if (fields)
		delete [] fields;

	delete stmt;
	OUTFUNC()
};


void XCDBStatementMySQL::Release()
{
	INFUNC( XCDBStatementMySQL::Release, hDB->myapp )
	delete this;
	OUTFUNC()
};


bool XCDBStatementMySQL::MakeStmt()
{
	INFUNC( XCDBStatementMySQL::MakeStmt, hDB->myapp );
	if( stmt->query->GetType() == SQL_STMT_NONE && stmt->pquery->GetType() == SQL_STMT_NONE )   // make sure we have a stmt to execute
		return false;
	stmt->MakeFull();
	OUTFUNCRET( true );
}

bool XCDBStatementMySQL::Execute()
{
	INFUNC( XCDBStatementMySQL::Execute, hDB->myapp );
	bool exec = true;
	if( strlen( stmt->sql_data ) == 0 )  // if we haven't made a string yet....
		exec = MakeStmt();
	if( exec ) 
		exec = Execute( stmt->sql_data );
	OUTFUNCRET( exec );		
}



bool XCDBStatementMySQL::Execute(char* sqlstr)
{
	INFUNC( XCDBStatementMySQL::Execute, hDB->myapp )

//	cout << "SQL: " << sqlstr << endl;

	// Make sure we have a string to execute
	if( strlen(sqlstr) == 0 )
		RETURNFUNC( false );

// TODO: Start protect with critical section

	// Execute the actual SQL statement
	if (mysql_query( hConn->conn, sqlstr ))
		HANDLE_MYSQL_ERROR( hConn->conn );

	// Obtain the results
	results = mysql_store_result( hConn->conn );

// TODO: End protect with critical section

	if (results) {
		// Determine the total number of fields
		field_count = mysql_num_fields(results);

		// Allocate space for field pointers
		fields = new XCField*[ field_count ];

		// Obtain the field definitions
		MYSQL_FIELD* fields_list = mysql_fetch_fields( results );
		for (unsigned int i=0; i<field_count; i++) {
			const char* colname = fields_list[i].name;
			XCField* fld = NULL;

			// first we need to get the field associated with this column.
			// if we don't have an indexer value, attempt to find it in the buffer
			// this should only be a special case for LoadAppdic and others that execute a sql statement directly
			if ( i>(stmt->ColCount+1) ) {
				int j;

				// Special case for count field
				if( stmt->query->GetType() == SQL_STMT_COUNT )
					fld = pBuf->GetFieldByIndex(0);
				else {
					for (j=0; j<pBuf->GetFieldCount(); j++) {
						fld = pBuf->GetFieldByIndex(j);
						char* field_name = fld->GetFieldName();

						if (strcmp( (char*)colname, field_name ) == 0)
							break;
					};
				};
				if (j == pBuf->GetFieldCount() )
					RAISETOOLSERROR_1PARAM( ERR_BAD_SQL_COL, colname );

			// otherwise just use the indexer		
			} else 
				fld = stmt->ColIndexer[i].field;

			// Set the field pointer
			fields[i] = fld;
		};

		// Obtain the first result
		MoveNext();
	} else {
		// No results, but should we have gotten some?
		if (mysql_errno( hConn->conn ))
			HANDLE_MYSQL_ERROR( hConn->conn );
	};

	OUTFUNCRET( true );
};


void XCDBStatementMySQL::MoveNext()
{
	row = mysql_fetch_row( results );
	IsEof = (row == NULL);

	if (!IsEof) {
		unsigned int num_fields = mysql_num_fields( results );
		unsigned long *lengths = mysql_fetch_lengths( results );
		for(unsigned int i = 0; i < field_count; i++)
		{
			const char* data = row[i];
			XCField* field = fields[i];

			int type = field->GetType();
			switch( type ) {
				case LITERAL_VAL:
					// Handle literal values separately
					if (data)
						field->SetLong( atoi( data ) );
					else
						field->Clear();
					break;

				case XCBLOB:
					// Handle Blob data
					if (lengths[i]) {
						field->InitBLOB( lengths[i] );
						memcpy( field->GetBLOB(), data, lengths[i] );
					} else {
						// No data, make sure it's cleared
						field->Clear();
					};
					break;

				default:
					// MySQL returns everything as a string, so allow Protea to attempt conversion
					field->SetString( data );
					break;
			};
		}
	};
};


char *XCDBStatementMySQL::GetSQLString( char* buffer )
{
	stmt->GetFullString( buffer );
	return buffer;
}




////////////////////////////////////////
//
// XCSQLStatement
//
////////////////////////////////////////
XCSQLStatementMySQL::XCSQLStatementMySQL( XCDBConnectionMySQL* connection ) : XCSQLStatement()
{
	// Stash our connection
	hConn = connection;
}

XCSQLStatementMySQL::XCSQLStatementMySQL( XCDBConnectionMySQL* connection, XCSQLGen* query ) : XCSQLStatement( query )
{
	// Stash our connection
	hConn = connection;
}



int XCSQLStatementMySQL::GetValue( XCField* fld, char *start )
{
	INFUNC( XCSQLStatementMySQL::GetValue, NULL );

	char* cursor;
	int type = fld->GetType();

	if (!start) {
		cursor = &data[ strlen( data ) ];
		start = cursor;
	} else
		cursor = start;

	if (type != XCBLOB) {
		// Don't support parameter binding
		return GetValueNoParam( fld, start );
	}

	// Special case for blobs
	*cursor++ = '\'';
	cursor += mysql_real_escape_string( hConn->conn, cursor, (char*)fld->GetBLOB(), fld->GetBLOBSize() );
	*cursor++ = '\'';

	// Null terminate
	*cursor = 0;

	// Check for null field values
	if (*start==0) {
		if (fld->GetFlags() & FLAG_ALLOW_NULL) {
			// The field is NULL, but NULL values are OK
			cursor = start;
			cursor += sprintf( cursor, "NULL" );
		} else {
			// The field is NULL, but NULL values are BAD and would cause a database error
			RAISETOOLSERROR_1PARAM( -1261, fld );
		};
	};

	OUTFUNCRET( (cursor - start) );
}


int XCSQLStatementMySQL::GetDataType( XCField* fld, char* start, bool& do_not_null )
{
	INFUNC( XCSQLStatementMySQL::GetDataType, pquery->GetApp() );
	char *data = start;
	int type = fld->GetType();
	int retval;

	if (type == XCBLOB) {
		data += sprintf( data, "BLOB" );
		retval = (data - start);
	} else if (type == DATE_TIMESTAMP) {
		data += sprintf( data, "DATETIME" );
		retval = (data - start);
	} else {
		retval = XCSQLStatement::GetDataType( fld, start, do_not_null );
	};

	OUTFUNCRET( retval );
}
